package com.hqbzl.c1.cass;

import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

@Slf4j
public class TestCas {


    public static void main(String[] args) {
//        final Account account = new UnSafeAccount(1000);
//        Account.demo(account);
//        System.out.println("====================================================");
//        final AccountCas accountCas = new AccountCas(new AtomicInteger(1000));
//        Account.demo(accountCas);
//        System.out.println("====================================================");
//        final AccountCas2 accountCas2 = new AccountCas2(new AtomicInteger(1000));
//        Account.demo(accountCas2);
//        System.out.println("====================================================");
        final AccountAtomicReference accountAtomicReference = new AccountAtomicReference(new BigDecimal("1000"));
        Account.demo(accountAtomicReference);

    }

    public static interface Account {

        Integer getBalance();

        void withdraw(Integer amount);


        static void demo(Account account) {
            System.out.println("account before = " + account.getBalance());
            final long start = System.currentTimeMillis();
            CountDownLatch cdt = new CountDownLatch(1000);
            for (int i = 0; i < 1000; i++) {
                new Thread(() -> {
                    account.withdraw(1);
                    cdt.countDown();
                }, "th-" + i).start();
            }
            try {
                cdt.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            long useTime = System.currentTimeMillis() - start;
            System.out.println("account.getBalance() after = " + account.getBalance() + " useTime: " + useTime);
        }
    }

    static class UnSafeAccount implements Account {
        private Integer balance;

        UnSafeAccount(Integer balance) {
            this.balance = balance;
        }

        @Override
        public void withdraw(Integer amount) {
            this.balance -= amount;
        }

        @Override
        public Integer getBalance() {
            return this.balance;
        }

    }

    static class AccountCas implements Account {
        private AtomicInteger balance;

        AccountCas(AtomicInteger balance) {
            this.balance = balance;
        }

        @Override
        public void withdraw(Integer amount) {
            while (true) {
                int curr = balance.get();
                int after = curr - amount;
                if (balance.compareAndSet(curr, after)) {
                    break;
                } else {
                    log.debug("=================================>{}", Thread.currentThread().getName());
                }
            }
        }

        @Override
        public Integer getBalance() {
            return this.balance.get();
        }
    }

    static class AccountCas2 implements Account {
        private AtomicInteger balance;

        AccountCas2(AtomicInteger balance) {
            this.balance = balance;
        }

        @Override
        public void withdraw(Integer amount) {
            this.balance.addAndGet(-1 * amount);
        }

        @Override
        public Integer getBalance() {
            return this.balance.get();
        }
    }

    static class AccountAtomicReference implements Account {
        private AtomicReference<BigDecimal> balance;

        public AccountAtomicReference(BigDecimal balance) {
            this.balance = new AtomicReference<>(balance);
        }

        @Override
        public void withdraw(Integer amount) {
            while (true) {
                final BigDecimal curr = balance.get();
                final BigDecimal after = curr.subtract(new BigDecimal(String.valueOf(amount)));
                if (balance.compareAndSet(curr, after)) {
                    break;
                } else {
                    log.debug("=================================>{}", Thread.currentThread().getName());
                }
            }
        }

        @Override
        public Integer getBalance() {
            return this.balance.get().intValue();
        }
    }
}
